package com.iceclean.util;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @author : Ice'Clean
 * @date : 2021-05-01
 *
 * JDBC 封装类
 */
public class JdbcUtil {

    /**
     * 自定义数据库连接池
     * 减少每次都新建连接时的消耗
     */
    private static final MyConnectionPool MY_CONNECTION_POOL = new MyConnectionPoolImpl();

    /**
     * 使用本地线程存放数据库连接
     * 实现一个事务中的所有数据库操作公用一个数据库连接
     */
    private static final ThreadLocal<Connection> THREAD_LOCAL = new ThreadLocal<>();

    private static MyLogger myLogger = new MyLogger("util");

    //设置数据库连接池
    static {
        // 初始化连接池
        try {
            MY_CONNECTION_POOL.initPool();
        } catch (SQLException sqlException) {
            sqlException.printStackTrace();
        }
    }

    /**
     * 工具类的构造方法私有
     */
    private JdbcUtil() {
        myLogger.writeInfo("日志生效了！");
    }

    /**
     * 获取数据库连接对象
     * @return 数据库连接对象
     * @throws SQLException 异常抛出
     */
    public static Connection getConnection() throws SQLException {
        // 首先从本地线程中寻找
        Connection conn = THREAD_LOCAL.get();

        // 没有的话再从数据库连接池中取出一个连接，然后保存到本地线程中
        if (conn == null) {
            conn = MY_CONNECTION_POOL.getConnection();
            THREAD_LOCAL.set(conn);
        }

        return conn;
    }

    /**
     * 释放资源
     * @param conn 需要释放的数据库连接对象
     * @param stmt 需要释放的数据库操作对象
     * @param ps 需要释放的释放数据库预编译操作对象
     * @param rs 需要释放的结果集
     */
    public static void closeAll(Connection conn, Statement stmt, PreparedStatement ps, ResultSet rs)
    {
        // 释放结果集
        try {
            if(rs!=null)  {
                rs.close();
            }
            if(ps!=null)  {
                ps.close();
            }
            if(stmt!=null)  {
                stmt.close();
            }

            // 关闭连接时，需要移除本地线程中的连接
            if(conn!=null) {
                THREAD_LOCAL.remove();
                MY_CONNECTION_POOL.closeConnection(conn);
            }
        } catch (SQLException sqlException) {
            sqlException.printStackTrace();
            myLogger.writeInfo(sqlException.getMessage());
        }
    }

    /**
     * 利用反射执行 sql 查询
     * @param sql 要执行的 sql 语句
     * @param entityClass 返回结果的实体类
     * @param objects 预编译需要填充的数据
     * @param <T> 实体类类型
     * @return 查询结果实体类集合
     */
    public static <T> List<T> query(String sql, Class<T> entityClass, Object...objects) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        List<T> data = new ArrayList<>();

        try {
            // 创建连接和预编译
            conn = getConnection();
            ps = conn.prepareStatement(sql);

            // 填充占位符
            int num = 1;
            for (Object obj : objects){
                ps.setObject(num, obj);
                num++;
            }

            // 获取结果集以及结果集对应的信息
            rs = ps.executeQuery();
            ResultSetMetaData metaData = rs.getMetaData();
            int col = metaData.getColumnCount();

            // 遍历取出所有数据
            while (rs.next()){
                // 新实例化一个实体类
                T entity = entityClass.newInstance();

                // 循环遍历每一个字段，给实体类赋值
                for (int i = 1; i <= col; i++) {
                    // 获取字段名
                    String columnName = metaData.getColumnName(i);

                    // 获取该字段对应的属性 Field 和 setter 方法
                    String fieldName = changeToFiledName(columnName);
                    Field field = entityClass.getDeclaredField(fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1));
                    Method setter = entityClass.getMethod("set" + fieldName, field.getType());

                    // 执行 setter 方法为该字段赋值
                    // 将日期转化成字符串
                    if ("TIMESTAMP".equals(metaData.getColumnTypeName(i))){
                        setter.invoke(entity, rs.getObject(i).toString());
                    } else{
                        setter.invoke(entity, rs.getObject(i));
                    }
                }

                // 将实体类放入数组
                data.add(entity);
            }

        } catch (InstantiationException | IllegalAccessException | SQLException | NoSuchFieldException | NoSuchMethodException | InvocationTargetException e) {
            e.printStackTrace();
            myLogger.writeInfo(e.getMessage());
        } finally {
            // 连接应该在 Service 层中才关闭
            closeAll(null, null, ps, rs);
        }

        return data;
    }

    /**
     * 执行插入语句
     * 方案一，使用反射
     * @param entity 要插入的实体类
     * @param <T> 实体类的类型
     * @return 新对象的 id 值
     */
    public static <T> int insert(String tableName, T entity) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        // 获取所有属性名（字段名）
        List<String> fieldNames = new ArrayList<>();
        List<String> columnNames = new ArrayList<>();
        Field[] fields = entity.getClass().getDeclaredFields();
        for(Field field : fields){
            fieldNames.add(field.getName());
            columnNames.add(changeToColumnName(field.getName()));
        }

        // 获取 sql 语句
        // 这里使用了 getNewColumnName ，只对需要的数据（由各个实体类设置）进行填充，其余由数据库生成
        String sql = SqlEditor.getInsert(tableName, columnNames.toArray());

        try {
            // 创建连接和预编译
            conn = getConnection();
            ps = conn.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);

            // 将实体类中每一个值更新到预编译语句中
            int i = 1;
            for(String fieldName : fieldNames){
                // 获取 getter 方法
                java.lang.reflect.Method getter = entity.getClass().getMethod("get" + changeToFiledName(fieldName));

                // 获取字段的值，并写入到预编译语句中
                ps.setObject(i, getter.invoke(entity));
                i++;
            }

            ps.executeUpdate();

            // 返回 id 值
            rs = ps.getGeneratedKeys();
            if (rs.next()){
                return rs.getInt(1);
            }
        } catch (SQLException | NoSuchMethodException | IllegalAccessException | InvocationTargetException sqlException){
            sqlException.printStackTrace();
            myLogger.writeInfo(sqlException.getMessage());
        } finally {
            // 连接应该在 Service 层中才关闭
            closeAll(null, null, ps, rs);
        }

        return -1;
    }

    /**
     * 执行更新语句
     * @param condition 更新的条件
     * @param entity 要更新的实体类
     * @return 是否更新成功
     */
    public static <T> boolean update(String tableName, String condition, T entity) {
        Connection conn = null;
        PreparedStatement ps = null;

        // 获取所有属性名（字段名）
        List<String> fieldNames = new ArrayList<>();
        List<String> columnNames = new ArrayList<>();
        Field[] fields = entity.getClass().getDeclaredFields();
        for(Field field : fields){
            fieldNames.add(field.getName());
            columnNames.add(changeToColumnName(field.getName()));
        }

        // 获取 sql 语句
        String sql = SqlEditor.getUpdate(tableName, condition, columnNames.toArray());

        try {
            // 创建连接和预编译
            conn = getConnection();
            ps = conn.prepareStatement(sql);

            // 将实体类中每一个值更新到数据库中
            int i = 1;
            for(String columnName : fieldNames){
                // 获取 getter 方法
                Method getter = entity.getClass().getMethod("get" + changeToFiledName(columnName));

                // 获取字段的值，并写入到预编译语句中
                ps.setObject(i, getter.invoke(entity));
                i++;
            }

            ps.executeUpdate();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            myLogger.writeInfo(e.getMessage());
        } finally {
            // 连接应该在 Service 层中才关闭
            closeAll(null, null, ps, null);
        }

        return false;
    }

    /**
     * 执行删除语句
     * @param tableName 从那个表中删除
     * @param condition 删除的条件
     * @return 是否删除成功
     */
    public static boolean delete(String tableName, String condition) {
        Connection conn = null;
        PreparedStatement ps = null;

        // 获取 sql 语句
        String sql = SqlEditor.getDelete(tableName, condition);

        try {
            // 创建连接和预编译
            conn = getConnection();
            ps = conn.prepareStatement(sql);

            return ps.executeUpdate() != 0;

        } catch (Exception e) {
            e.printStackTrace();
            myLogger.writeInfo(e.getMessage());
        } finally {
            // 连接应该在 Service 层中才关闭
            closeAll(null, null, ps, null);
        }

        return false;
    }

    /**
     * 将数据库中的字段名转化成对应实体类中的属性名
     * @param columnName 数据库中的字段名
     * @return 对应的属性名
     */
    private static String changeToFiledName(String columnName){
        // 存放最终的名字
        StringBuilder newName = new StringBuilder();

        // 以下划线为分隔符
        String[] partName = columnName.split("_");

        for (String part : partName){
            newName.append(part.substring(0, 1).toUpperCase()).append(part.substring(1));
        }

        return newName.toString();
    }

    /**
     * 将实体类中的属性名转化成对应数据库中的字段名
     * @param fieldName 实体类中的属性名
     * @return 对应的字段名
     */
    private static String changeToColumnName(String fieldName){
        // 存放最终的名字
        StringBuilder newName = new StringBuilder();

        // 在每个大写字母前面加下划线，作为分隔符，再进行分割
        String tempName = fieldName.replaceAll("[A-Z]", "_$0");
        String[] partName = tempName.split("_");

        for (String part : partName){
            newName.append(part.substring(0, 1).toLowerCase()).append(part.substring(1)).append("_");
        }

        // 删掉最后一个下划线
        newName.deleteCharAt(newName.length() - 1);

        return newName.toString();
    }
}
